#include "executor.h"
#include "shared\stlex.h"
#include <windows.h>
#include "RedirectionTarget.h"
#include "Wait.h"

std::vector<exec_context_t*> Executor::m_ctxs;
std::map<exec_id_t,std::string> Executor::m_cmds;
CRITICAL_SECTION Executor::cs_cmd;

Executor *Executor::self = NULL;


Executor::Executor()
{
	InitializeCriticalSection(&cs_cmd);
}

Executor::~Executor(){
	check_exes();
	delete_vector(m_ctxs);
	m_cmds.clear();
	DeleteCriticalSection(&cs_cmd);
}


#define ERROR_IF_FAIL(result) if(!result) {\
	log(#result);\
	throw("error");\
}
	


//handler_mode == 0 for stdout,
//handler_mode == 1 for stderr
bool process_output(char *str,std::string & outbuf,exec_context_t *ctx,int handler_mode)
{
		 outbuf += str;

		 while(true){

		    int sep_len = 2;

			size_t pos =  outbuf.find("\r\n");
			if(pos == std::string::npos){ 
				sep_len = 1;
				pos =  outbuf.find("\r");
				if(pos == std::string::npos)
					pos = outbuf.find("\n");
				if(pos == std::string::npos)
				break;
			}
			
			std::string subs = outbuf.substr(0,pos+sep_len);
			outbuf = outbuf.substr(pos+sep_len);
			if(handler_mode == 0)
				ctx->parser->on_stdout_line(ctx->thread_id,ctx->param,subs);
			else
				ctx->parser->on_stderr_line(ctx->thread_id,ctx->param,subs);

		 }

		 return true;
}

/*called when this executor reevie a output from stdout or stderr
ctx: the LPVOID param 
out: the output
target: the redirectiontarget name
*/		

void OnCtxOutput(LPVOID param,char *out,const std::string & target)
{
	exec_context_t *ctx = (exec_context_t*)param;
	if(target == "StdOut")
			process_output(out,ctx->out_buf,ctx,0);
	else if(target=="StdErr")
			process_output(out,ctx->err_buf,ctx,1);
	else
		assert(false);

}



DWORD WINAPI ConsoleThread(LPVOID param){
	DWORD l_dwResult = ERROR_SUCCESS;

	exec_context_t *ctx = (exec_context_t *)param;

	CRedirectionTarget m_RedirectedStdOut;
	CRedirectionTarget m_RedirectedStdErr;
	m_RedirectedStdOut.Reset ();
	m_RedirectedStdErr.Reset ();

	HANDLE hInRead = NULL,hInWriteTmp = NULL,hInWrite = NULL;

	try {
		m_RedirectedStdOut.Initialise (_T("StdOut"));
		m_RedirectedStdErr.Initialise (_T("StdErr"));

		if(ctx->support_stdin){
			SECURITY_ATTRIBUTES sa;	
			sa.nLength= sizeof(SECURITY_ATTRIBUTES);
			sa.lpSecurityDescriptor = NULL;
			sa.bInheritHandle = TRUE;

			ERROR_IF_FAIL(::CreatePipe(&hInRead,&hInWriteTmp,&sa,0));
			ERROR_IF_FAIL(::DuplicateHandle(GetCurrentProcess(),hInWriteTmp,GetCurrentProcess(),&hInWrite,0,FALSE,DUPLICATE_SAME_ACCESS));
			ERROR_IF_FAIL(::CloseHandle(hInWriteTmp));
			ctx->h_stdin = hInWrite;//stdin for the child process
		}

		// Stick that in your pipe and smoke it!
		STARTUPINFO l_StartupInfo = {0};
		 ZeroMemory(&(ctx->pi),sizeof(PROCESS_INFORMATION));
		 ZeroMemory(&l_StartupInfo,sizeof(STARTUPINFO));

		l_StartupInfo.cb = sizeof (l_StartupInfo);
		l_StartupInfo.dwFlags |= STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES;
		//l_StartupInfo.hStdInput = GetStdHandle (STD_INPUT_HANDLE);
		l_StartupInfo.hStdInput = hInRead?hInRead:(::GetStdHandle(STD_INPUT_HANDLE));
		l_StartupInfo.hStdOutput = m_RedirectedStdOut.GetPipeWritingHandle ();
		l_StartupInfo.hStdError = m_RedirectedStdErr.GetPipeWritingHandle ();

		//PROCESS_INFORMATION l_ProcessInformation = {0};

		 std::string cmdline = ctx->program + " " + ctx->args;

		 
		
		 LPTSTR l_lpszCommandLine = (LPSTR)cmdline.c_str();
		if (!CreateProcess (
			NULL,					// lpApplicationName
			l_lpszCommandLine,			// lpCommandLine
			NULL,						// lpProcessAttributes
			NULL,						// lpThreadAttributes
			TRUE,						// bInheritHandles
			0,							// dwCreationFlags
			NULL,						// lpEnvironment
			ctx->workdir.c_str(),						// lpCurrentDirectory
			&l_StartupInfo,				// lpStartupInfo
			&(ctx->pi)))		// lpProcessInformation
		{
			DWORD l_dwLastError = GetLastError ();
			(void) _ftprintf (stderr, _T("CreateProcess() failed with %ld.\n"), l_dwLastError);
			throw l_dwLastError;
		}

		//l_hProcess = l_ProcessInformation.hProcess;
		//l_hThread = l_ProcessInformation.hThread;

		
		CMyOverlapped *ovpOut = new CMyOverlapped(m_RedirectedStdOut,ctx);
		CMyOverlapped *ovpErr = new CMyOverlapped(m_RedirectedStdErr,ctx);

			 
		m_RedirectedStdOut.StartAsyncRead(ovpOut);
		m_RedirectedStdErr.StartAsyncRead(ovpErr);

		(void) Wait (
			INFINITE,		// p_dwTimeout
			FALSE,			// p_bWaitAll
			TRUE,			// p_bAlertable
			1,				// p_iNumHandles
			ctx->pi.hProcess);	// handle 1

		::GetExitCodeProcess(ctx->pi.hProcess,&l_dwResult);

		// Break the pipes by closing the writing ends of the pipes.
		m_RedirectedStdOut.CloseWritingEnd ();
		m_RedirectedStdErr.CloseWritingEnd ();		

		(void) Wait (
			INFINITE,										// p_dwTimeout
			TRUE,											// p_bWaitAll
			TRUE,											// p_bAlertable
			2,												// p_iNumHandles
			m_RedirectedStdOut.GetFinishedReadingEvent (),	// handle 1
			m_RedirectedStdErr.GetFinishedReadingEvent ());	// handle 2

		
		delete(ovpOut); ovpOut = 0;
		delete(ovpErr);  ovpErr = 0;
	}
	catch (DWORD dwException) {
		l_dwResult = dwException;
	}
	
	 if(ctx->parser) 
			ctx->parser->on_exit(ctx->thread_id,l_dwResult);

	 ctx->exited = true;

	return l_dwResult;
}

//check all progs and close the exited
void Executor::check_exes(){
	std::vector<exec_context_t*>::iterator it = m_ctxs.begin();
	while(it != m_ctxs.end()){
		if((*it)->exited){
			::CloseHandle((*it)->h_thread);
			delete((*it));
			it = m_ctxs.erase(it);
		}else
			it++;
	}

}

exec_id_t Executor::exec(char *exe,char *args,char *workdir,console_callback_t *parser,cparam_t param, bool support_stdin){

	DWORD thread_id = 0;

	exec_context_t *ctx = new exec_context_t();


	ctx->program = exe;
	ctx->args = args;
	ctx->workdir = workdir;
	ctx->param = param;
	//ctx->proc_id = 0;
	ctx->parser = parser;
	ctx->support_stdin = support_stdin;
	ctx->exited = false;
	


	check_exes();
	

	HANDLE hThread = ::CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)ConsoleThread,ctx,0,&thread_id);
	if(hThread){
		ctx->h_thread = hThread;
		ctx->thread_id = thread_id;
		m_ctxs.push_back(ctx);
	}else{
		delete ctx;ctx = 0;
		log("CreateThread failed:%d",GetLastError());
	}

	return thread_id;
}

int Executor::pause(exec_id_t proc){

	DWORD res = 0;

	std::vector<exec_context_t*>::iterator it = m_ctxs.begin();
	while(it != m_ctxs.end()){
		if((*it)->thread_id == proc){
			res = ::SuspendThread((*it)->pi.hThread);
			if(res == -1){
				log("SuspendThread error %d",GetLastError());
			}
			break;
		}
		it++;
	}

	return res;
}


int Executor::stop(exec_id_t proc){
	DWORD res = 0;

	std::vector<exec_context_t*>::iterator it = m_ctxs.begin();
	while(it != m_ctxs.end()){
		
		if((*it)->thread_id == proc){
			if((*it)->exited)
				return 0 ;

			BOOL ret = ::AttachConsole((*it)->pi.dwProcessId);
			if(ret){
				ret = ::GenerateConsoleCtrlEvent(CTRL_BREAK_EVENT, (*it)->pi.dwProcessId);
				if(!ret){
					log("GenerateConsoleCtrlEvent error %d",GetLastError());
				}
				ret = ::FreeConsole();
			}

			int ntry = 0;
			while(ntry < 3){
				res = ::WaitForSingleObject((*it)->pi.hProcess, 1000 * 2);
				if (res != WAIT_OBJECT_0)
					ret = ::TerminateProcess((*it)->pi.hProcess, -1);
				if(ret) break;
				ntry++;
			}

			//if(ntry == 3) ::TerminateThread((*it)->h_thread,0);
			break;
		}
		it++;
	}

	return res;
}




int Executor::resume(exec_id_t proc){


	DWORD res = 0;

	std::vector<exec_context_t*>::iterator it = m_ctxs.begin();
	while(it != m_ctxs.end()){
		if((*it)->thread_id == proc){
			res = ::ResumeThread((*it)->pi.hThread);
			break;
		}
		it++;
	}

	return res;
}




bool Executor::command(exec_id_t proc,const char *cmd)
{
	/*EnterCriticalSection(&cs_cmd);
	m_cmds.insert(std::make_pair(proc,cmd));
	LeaveCriticalSection(&cs_cmd);
	return true;*/

	BOOL res  = true;
		std::vector<exec_context_t*>::iterator it = m_ctxs.begin();
		while(it != m_ctxs.end()){
			if((*it)->thread_id == proc){

				assert((*it)->support_stdin);

				DWORD sz;
				BOOL res = ::WriteFile((*it)->h_stdin,cmd,strlen(cmd),&sz,NULL);
				break;
			}
			it++;
		}

		return res;
}

DWORD WINAPI CommandThread(LPVOID param)
{

	return true;

}

void Executor::process_command(){

	EnterCriticalSection(&cs_cmd);
	std::map<exec_id_t,std::string>::iterator mit = m_cmds.begin();
	while(mit != m_cmds.end()){

		exec_id_t proc = mit->first;
		std::string cmd = mit->second;

		std::vector<exec_context_t*>::iterator it = m_ctxs.begin();
		while(it != m_ctxs.end()){
			if((*it)->thread_id == proc){

				assert((*it)->support_stdin);

				DWORD sz;
				BOOL res = ::WriteFile((*it)->h_stdin,cmd.c_str(),cmd.length(),&sz,NULL);
				break;
			}
			it++;
		}

		mit++;
	}

	m_cmds.clear();
	LeaveCriticalSection(&cs_cmd);

}